<?php

if (!defined('UC_FILESYSTEM_ROOT')) 
{
    define('UC_FILESYSTEM_ROOT', dirname(__FILE__) . '/');
    require_once UC_FILESYSTEM_ROOT . 'UC_PHPFileSystemAutoloader.php';
}

/**
 * A class to hold a collection of objects of  the same type.
 * 
 * There are tow purposes:
 * - to ensure that all elements are of the same type
 * - to be enumerable by the foreach loop
 * 
 */

abstract class UC_PHPTypedCollection implements Countable, Iterator, ArrayAccess
{
    /**
     * Child classes pass the collection type to this constructor
     * 
     * @param string $typeName The name of the underlying class
     * @throws InvalidArgumentException If the class can't be found by reflection
     */
    
    public function __construct($typeName) 
    {
        if(class_exists($typeName))
        {
            $this->_type = new ReflectionClass($typeName);
        }
        else
        {
            throw new InvalidArgumentException(
                "The class $typeName does not exist.");
        }
    }
    
    /* Implement Countable */
    
    /**
     * Returns the count of the elements in the collection
     * @return int 
     */
    
    public function count() 
    {
        return count($this->_innerArray);
    }
    
    /* Implement Iterator */
    
    /**
     * Returns the current element of the collection
     * @return Object 
     */
    
    public function current() 
    {
        return current($this->_innerArray);
    }
    
    /**
     * Return the key of the current element
     * @return int 
     */
    
    public function key() 
    {
        return key($this->_innerArray);
    }
    
    /**
     * Move forward to next element
     */
    
    public function next() 
    {
        next($this->_innerArray);
    }
    
    /**
     * Rewind to its first element 
     */
    
    public function rewind() 
    {
        reset($this->_innerArray);
    }
    
    /**
     * Checks if the current position is valid
     * @return bool 
     */
    
    public function valid() 
    {
        return $this->current() !== false;
    }
    
    /* Implement ArrayAccess */
    
    /**
     * Whether an offset exists
     * @param int $offset
     * @return bool 
     */
    
    public function offsetExists($offset) 
    {
        return isset($this->_innerArray[$offset]);
    }
    
    /**
     * Returns the value at the specified offset
     * @param int $offset
     * @return int 
     */
    
    public function offsetGet($offset) 
    {
        if(isset($this->_innerArray[$offset]))
        {
            return $this->_innerArray[$offset];
        }
        else
        {
            return null;
        }
    }
    
    /**
     * Assigns a value to the specified offset
     * @param int $offset 
     * @param mixed $object 
     * @throws InvalidArgumentException If $object is not of the underlying type
     */
    
    public function offsetSet($offset, $object) 
    {
        if ($object instanceof $this->_type->name)
        {
            if (is_null($offset)) 
            {
                $this->_innerArray[] = $object;
            }
            else 
            {
                $this->_innerArray[$offset] = $object;
            }
        } 
        else 
        {
            throw new InvalidArgumentException(
                "Object needs to be a {$this->_type->name} instance.");
        }
    }
    
    /**
     * Unsets an offset
     * @param int $offset 
     */
    
    public function offsetUnset($offset) 
    {
        unset($this->_innerArray[$offset]);
        $this->_innerArray = array_values($this->_innerArray);
    }
    
    /* TypedCollection functions */
    
    /**
     * Adds an object to the collection.
     * @param mixed $object The object to add to the collection
     */
    
    public function add($object)
    {
        $this->offsetSet(null, $object);
    }
    
    /**
     * Removes all elements from the collection 
     */
    
    public function clear()
    {
        $this->_innerArray = array();
    }
    
    /**
     * Checks if an object belongs to the collection
     * @param mixed $object
     * @return bool 
     */
    
    public function contains($object)
    {
        return in_array($object, $this->_innerArray, true);
    }
    
    /**
     * Return an object index
     * @param mixed $object
     * @return int 
     */
    
    public function indexOf($object)
    {
        return array_search($object);
    }
    
    /**
     * Inserts an object at the specified offset in the collection
     * @param int $offset
     * @param mixed $object
     * @throws InvalidArgumentException If the object is not of the underlying type
     * @throws OutOfRangeException If the offset does not exist
     */
    
    public function insert($offset, $object)
    {
        if(array_key_exists($offset, $this->_innerArray))
        {
            if($object instanceof $this->_type->name)
            {
                $tempArray = array($object, $this->offsetGet($offset));
                array_splice($this->_innerArray, $offset, 1, $tempArray);
                $this->_innerArray = array_values($this->_innerArray);
            }
            else
            {
                throw new InvalidArgumentException(
                    "Object needs to be a $this->_type->name instance.");
            }
        }
        else
        {
            throw new OutOfRangeException(
                "The index $offset does not exist in the collection.");
        }
    }

    /**
     * Removes the specified object from the collection
     * @param mixed $object 
     */
    
    public function remove($object)
    {
        if($this->contains($object))
        {
            $this->offsetUnset(array_search($object, $this->_innerArray));
            $this->_innerArray = array_values($this->_innerArray);
        }
    }    
    
    /**
     * Removes the object at the specified offset in the collection
     * @param int $offset 
     */
    
    public function removeAt($offset)
    {
        $this->offsetUnset($offset);
        $this->_innerArray = array_values($this->_innerArray);
    }
    
    /**
     * The array that contains collection elements
     * @var array 
     */
    
    protected $_innerArray = array();
    
    /**
     * The collection type
     * @var ReflectionClass
     */
    
    protected $_type;
}

?>

